
import 'dart:async';
import 'dart:io';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:uuid/uuid.dart';
import 'package:flu_wake_lock/flu_wake_lock.dart';
import 'package:xsdk_flutter/xsdk/framework/base/xsdk_widget_stateful.dart';
import 'package:xsdk_flutter/xsdk/framework/bean/x_enum.dart';
import 'package:xsdk_flutter/xsdk/framework/base/xsdk_function.dart';
import 'package:xsdk_flutter/xsdk/framework/page/x_page_state.dart';
import 'package:xsdk_flutter/xsdk/operation/x_constant_temp.dart';
import 'package:xsdk_flutter/xsdk/utils/json/x_utils_json.dart';
import 'package:xsdk_flutter/xsdk/utils/x_utils_color.dart';
import 'package:xsdk_flutter/xsdk/utils/x_utils_log.dart';
import 'package:xsdk_flutter/xsdk/utils/x_utils_number.dart';
import 'package:xsdk_flutter/xsdk/utils/x_utils_string.dart';
import 'package:xsdk_flutter/xsdk/utils/x_utils_style.dart';
import 'package:xsdk_flutter/xsdk/utils/x_utils_widget.dart';
import 'package:image_picker/image_picker.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:vibration/vibration.dart';
import 'package:app_analysis/app_analysis.dart';
import 'package:restart_app/restart_app.dart';
/*

其他:
https://pub-web.flutter-io.cn/packages/flutter_background_service

状态管理 https://flutter.cn/docs/development/data-and-backend/state-mgmt/intro
1.简单可以使用 State 和 setState() 管理你的应用中的自身的状态更新UI
2.可以使用provider 管理跨界面的通知UI更新
dependencies:
  provider: ^6.0.5
import 'package:provider/provider.dart';


原生+flutter模式:
android原生:
FlutterEngine flutterEngine;
String methodChannelName="reabam_flutter_plugin";
public MethodChannel methodChannel;
private void initFlutter
    flutterEngine = new FlutterEngine(this);
    flutterEngine.getDartExecutor().executeDartEntrypoint(
            DartExecutor.DartEntrypoint.createDefault()
    );
    FlutterEngineCache
            .getInstance()
            .put(App.flutterEngine_analytic, flutterEngine);
    //用于通知传递数据到flutter界面
    methodChannel=new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(),methodChannelName);
    //接收flutter通讯消息
        methodChannel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {
            @Override
            public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
                try{
                    if(call.method.equalsIgnoreCase("FlutterData")){
                        String json=call.arguments.toString();
                        L.sdk("---来自flutter模块的消息json= "+json);
                    }
                }catch (Exception e){
                    L.sdk(e);
                }
            }
        });
}

//跳转到flutter界面:
startActivity(FlutterActivity.withCachedEngine(App.flutterEngine_analytic).build(activity));

//原生发送消息给flutter:
((App)getApplication()).methodChannel.invokeMethod("NativeData","12345asdf");

flutter部分:
//在sate中注册:
MethodChannel platform=const MethodChannel('reabam_flutter_plugin');
//界面加载完成时注册:
platform.setMethodCallHandler(methodCallHandler);
//接收原生通信消息
Future<dynamic> methodCallHandler(MethodCall call) async{
  XLogUtils.printLog('---reabam_flutter_plugin, methodCallHandler,method=${call.method}');
  if('NativeData'==call.method){
    String value=call.arguments.toString();
    XLogUtils.printLog('---reabam_flutter_plugin, methodCallHandler,value=$value');
  }
  XFlutterUtils.startPage(context, XPage(()=>PageStateAnalyseIndex()));
}

//flutter发送消息给原生:(适用原声+flutter,也适用 flutter+原生)
Future<dynamic> sendMsgToPlatform(String soName) async{
    final result = await methodChannel.invokeMethod<String>('xxx_callMethod',XJsonUtils.obj2Json(methodCommand));
    return result;
  }


注意:返回原生后,如果后续调整不同flutter界面,需要清除flutter缓存(重新建flutterEngine,FlutterEngineCache等)

 */

/*
 设备 android iOS windows mac linux

 屏幕方向: https://pub.flutter-io.cn/packages/native_device_orientation

截屏: https://pub.flutter-io.cn/packages/screenshot
https://pub.flutter-io.cn/packages/share_files_and_screenshot_widgets

锁屏: https://pub.flutter-io.cn/packages/flutter_screen_lock
https://pub.flutter-io.cn/packages/flutter_lock_screen

录屏: https://pub.flutter-io.cn/packages/screen_recorder


其他:

通知数(app图标)
https://pub.flutter-io.cn/packages/flutter_app_badger

dependencies:
  flutter_app_badger: ^1.5.0

import 'package:flutter_app_badger/flutter_app_badger.dart';


通知栏消息
https://pub.flutter-io.cn/packages/flutter_local_notifications
dependencies:
  flutter_local_notifications: ^13.0.0
import 'package:flutter_local_notifications/flutter_local_notifications.dart';


计时器
https://github.com/hukusuke1007/stop_watch_timer

 */
//界面携带数据返回上一界面的处理回调接收方法
typedef XPageCallback<T> = void Function(T result);


abstract class XFlutterUtils{

  //已在XSDKApplication中,初始化,用于关闭键盘等
  static BuildContext? applicationContext;
  //当前pageSate的Context,用于拦截跳转某个界面(如api错误码指定跳转登录界面)
  static BuildContext? currentContext;

  /*
  flutter发送消息给原生(适用原声+flutter,也适用 flutter+原生)

  //设置接收原生发送flutter通知消息
    methodChannel.setMethodCallHandler(nativeCallback==null?null:(MethodCall call){
      XLogUtils.printLog('---XSDK_Flutter_plugin接收原生通知消息: method=${call.method},data=${call.arguments}');

      if(this.nativeCallback!=null){
        XMethodCommand data=XMethodCommand(
            call.method,call.arguments
        );
        nativeCallback(XJsonUtils.obj2Json(data));
      }

      return Future(() => null);
    });
   */
  static Future<dynamic> sendMsgToNative(
      MethodChannel methodChannel,//final methodChannel = const MethodChannel('xsdk_flutter_plugin');
      String callMethodTag,
      String json
      ) async{
    final result = await methodChannel.invokeMethod<String>(callMethodTag,json);
    return result;
  }

  //接收原生通知消息
  static void receiveNativeMessageCallback(
      MethodChannel methodChannel,//final methodChannel = const MethodChannel('xsdk_flutter_plugin');
      XFunctionResultCallback? callback
      ){

    methodChannel.setMethodCallHandler(callback==null?null:(MethodCall call){
      XLogUtils.printLog('---XSDK_Flutter接收原生通知消息: method=${call.method},data=${call.arguments}');

      Map<String,dynamic> resMap={};
      resMap['type']=call.method;
      resMap['data']=call.arguments;
      callback(resMap);

      return Future(() => null);
    });
  }

  //注意:不能在XDKApp中赋值初始化context,因为App的的context不含Navigator,否则使用时会报错:Navigator operation requested with a context that does not include a Navigator
  //static late BuildContext context;

  // XFlutterUtils._(); // 把构造方法私有化
  // static XFlutterUtils? _instance;
  // // 通过 getInstance 获取实例
  // static XFlutterUtils? getInstance() {
  //   _instance ??= XFlutterUtils._();
  //   return _instance;
  // }

  //获得当前应用对应的硬件系统名称
  static String getSystemName() {
    //注意:实测编译成web应用时报错,Unsupported operation: Platform._operatingSystem
    String systemName='Other';
    try{
      if (Platform.isAndroid) {
        systemName = 'Android'; //系统名称
      } else if (Platform.isIOS) {
        systemName = 'iOS'; //系统名称
      }else if (Platform.isFuchsia) {
        systemName = 'Fuchsia'; //系统名称
      }else if (Platform.isWindows) {
        systemName = 'Windows'; //系统名称
      } else if (Platform.isLinux) {
        systemName = 'Linux'; //系统名称
      }else if (Platform.isMacOS) {
        systemName = 'MacOS'; //系统名称
      }
    }catch (e) {
      systemName = 'Web'; //系统名称
    }
    return systemName;
  }

  //判断是否android或者iOS系统(某些第三方库只支持移动端平台,用于判断)
  static bool isAndroidOriOSSystem(){
    String sysName=getSystemName();
    if(sysName=='Android'||sysName=='iOS'){
      return true;
    }
    return false;
  }

  //测试判断web,有些系统不支持js库导致报错,因此不能使用kIsWeb去适配各个平台
  // static bool get isiOS => !kIsWeb && Platform.isIOS;
  // static bool get isAndroid => !kIsWeb && Platform.isAndroid;
  // static bool get isWindows => !kIsWeb && Platform.isWindows;
  // static bool get isWeb => kIsWeb;

  static bool isiOS(){
    String sysName=getSystemName();
    if(sysName=='iOS'){
      return true;
    }
    return false;
  }

  static bool isAndroid(){
    String sysName=getSystemName();
    if(sysName=='Android'){
      return true;
    }
    return false;
  }


  static bool isWindows(){
    String sysName=getSystemName();
    if(sysName=='Windows'){
      return true;
    }
    return false;
  }
  static bool isMacOS(){
    String sysName=getSystemName();
    if(sysName=='MacOS'){
      return true;
    }
    return false;
  }

  /*
  获得应用id,
  https://pub-web.flutter-io.cn/packages/package_info_plus/install
  dependencies:
    package_info_plus: ^4.1.0
  import 'package:package_info_plus/package_info_plus.dart';
   */
  static Future<PackageInfo> getAppPackageInfo() async{

    PackageInfo packageInfo = await PackageInfo.fromPlatform();

    // String appName = packageInfo.appName;
    // String packageName = packageInfo.packageName;
    // String version = packageInfo.version;
    // String buildNumber = packageInfo.buildNumber;

    return packageInfo;
  }

  //获得系统字体缩放比例
  static double getSystemFontDefaultScale(BuildContext context){
    return MediaQuery.textScalerOf(context).scale(10)/10;
  }

  /*
  设备硬件id(唯一)  ANDROID IOS LINUX MACOS WEB WINDOWS
  https://pub.flutter-io.cn/packages/platform_device_id/install
  dependencies:
    platform_device_id: ^1.0.1
  import 'package:platform_device_id/platform_device_id.dart';

  Windows: BIOS UUID e.g. 99A4D301-53F5-11CB-8CA0-9CA39A9E1F01
  Linux: BIOS UUID e.g. 32a70060-2a39-437e-88e2-d68e6154de9f
  Mac: IOPlatformUUID e.g. 02662E79-E342-521C-98EA-D4C18B61FEF3
  Android: AndroidId e.g. 9774d56d682e549c
  IOS: IdentifierForVendor e.g. 9C287922-EE26-4501-94B5-DDE6F83E1475
  Web:UserAgent e.g. Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36

   */
  static Future<String?> getDeviceId() async{
    //String? deviceId = await PlatformDeviceId.getDeviceId;
    String? deviceId = XFlutterUtils.getUUID();
    return deviceId;
  }

  /*
  设备硬件信息 ANDROID IOS LINUX MACOS WEB WINDOWS
  https://pub.flutter-io.cn/packages/device_info_plus
  dependencies:
    device_info_plus: ^8.1.0
  import 'package:device_info_plus/device_info_plus.dart';

   deviceModel,productName等信息

   */
  static Future<Map<String, dynamic>> getDeviceInfo()async {
    DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();

    String? model;//型号
    String? version;
    String? product;
    String? brand;//品牌
    String? id;
    dynamic other;

    String systemName=XFlutterUtils.getSystemName();

    if(systemName=='Android'){
      AndroidDeviceInfo androidDeviceInfo= await deviceInfoPlugin.androidInfo;
      model=androidDeviceInfo.model;
      product=androidDeviceInfo.product;
      brand=androidDeviceInfo.brand;
      version=androidDeviceInfo.version.release;
      id=androidDeviceInfo.serialNumber;
      other=androidDeviceInfo.version.toMap();

    }else if(systemName=='iOS'){
      IosDeviceInfo iosDeviceInfo=await deviceInfoPlugin.iosInfo;
      model=iosDeviceInfo.model;
      version=iosDeviceInfo.systemVersion;
      brand='Apple';
      id=iosDeviceInfo.identifierForVendor;

    }else if(systemName=='MacOS'){
      MacOsDeviceInfo macOsDeviceInfo=await deviceInfoPlugin.macOsInfo;
      model=macOsDeviceInfo.model;
      version=macOsDeviceInfo.kernelVersion;
      brand='Apple';
      id=macOsDeviceInfo.systemGUID;

    }else if(systemName=='Windows'){
      WindowsDeviceInfo windowsDeviceInfo=await deviceInfoPlugin.windowsInfo;
      model=windowsDeviceInfo.productName;
      version=windowsDeviceInfo.releaseId;
      id=windowsDeviceInfo.deviceId;

    }else if(systemName=='Linux'){
      LinuxDeviceInfo linuxDeviceInfo=await deviceInfoPlugin.linuxInfo;
      model=linuxDeviceInfo.name;
      version=linuxDeviceInfo.version;
      id=linuxDeviceInfo.machineId;

    }else if(systemName=='Web'){
      WebBrowserInfo webBrowserInfo=await deviceInfoPlugin.webBrowserInfo;
      model=webBrowserInfo.browserName.name;
      version=webBrowserInfo.appVersion;
      id='${webBrowserInfo.userAgent}_${webBrowserInfo.vendor}_${webBrowserInfo.vendorSub}';
    }

    //全部信息
    // final deviceInfo = await deviceInfoPlugin.deviceInfo;
    // XLogUtils.printLog(deviceInfo.data.toString());

    return {
      'systemName':systemName,
      'model':model,
      'version':version,
      'product':product,
      'brand':brand,
      'id':id,
      'other':other,
    };
  }


  /*
  强制屏幕方向 竖屏/横屏 在build widget之前调用(即在onCreate或onStart中调用)
  注意:如果不重新设置则后面的界面都一直沿用,因此进入和退出都要设置一下,需要重写界面的finishPage方法,添加XFlutterUtils.setScreenOrientation(true);
  也可以使用RotatedBox(quarterTurns: 1, child: Scaffold(...)) // 横屏显示 替代,
   */
  static void setScreenOrientation(bool isPortrait){
    //注意的是在使用的时候一定要保证先执行
    //WidgetsFlutterBinding.ensureInitialized();
    if(isPortrait){
      //竖屏
      SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
    }else{
      //横屏
      if(isAndroid()){
        //android
        SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeLeft]);
      }else{
        //iOS
        SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeRight]);
      }

    }
  }

  static bool getIsScreenPortrait(BuildContext context) {
    final Orientation orientation = MediaQuery.of(context).orientation;
    return orientation==Orientation.portrait;
  }

  // 判断是否是暗黑模式
  static bool isDarkMode() {
    bool isDarkMode;
    const ThemeMode themeMode = ThemeMode.system;
    if (themeMode == ThemeMode.light || themeMode == ThemeMode.dark) {
      isDarkMode = themeMode == ThemeMode.dark;
    } else {
      isDarkMode = WidgetsBinding.instance.window.platformBrightness == Brightness.dark;
    }
    return isDarkMode;
  }

  /*
  获得当前Theme的primaryColor
 */
  static Color getPrimaryColorOfTheme(BuildContext context) {
    return Theme.of(context).primaryColor;
  }

  /*
  获得屏幕宽度,
  注意:不能在pagestate的initState()或onCreate()中调用,否则报错;
   */
  static double getScreen(BuildContext context,{bool isWidth=true}){
    if(isWidth){
      return MediaQuery.of(context).size.width;
    }else{
      return MediaQuery.of(context).size.height;
    }
  }
  /*
  获得屏幕分辨率,
  注意:不能在pagestate的initState()或onCreate()中调用,否则报错;
   */
  static double getScreenPX({bool isWidth=true}){

    //像素比: dpr = window.devicePixelRatio;
    if(isWidth){
      return window.physicalSize.width;
    }else{
      return window.physicalSize.height;
    }
  }
  /*
  获得状态栏高度
  注意:不能在pagestate的initState()或onCreate()中调用,否则报错;要在onBuildWidget中获取,且当前界面的状态栏是显示的
   */
  static double getStatusBarHeight(BuildContext context){

    //有刘海的屏幕:44 没有刘海的屏幕为20
    // final statusBarHeight = mediaQueryData.padding.top;

    // 有刘海的屏幕:34 没有刘海的屏幕0
    // final bottomHeight = mediaQueryData.padding.bottom;

    return MediaQuery.of(context).padding.top;
  }


  /*
  全局设置状态栏(全屏) 在build widget之前调用(即在onCreate或onStart中调用)
  注意: android全屏(解决隐藏状态栏后依然占位问题)要在style.xml中添加: <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
  实测:android每次重设SystemUiOverlay.top,第一次点击屏幕失效;
  也可以在page中对AppBar设置单页状态栏: xpagestate.setXStatusBarColor(),但返回上一页面会沿用当前颜色,跳转新页面则不会
  不适用美团收银机,商米T2,导致其无法滑出底部导航栏
   */
  static bool? _systemStatusBarIsTextColorLightLast;
  static bool? _systemStatusBarIsShowLast;
  static Color? _systemStatusBarBackgroundColorLast;
  static void setSystemStatusBar(
      bool isShow,
      {
        bool isOnlyReSetColor=false,
        bool isTextColorLight=false,
        Color backgroundColor=Colors.transparent,
      }
      ){
    //注意的是在使用的时候一定要保证先执行
    //WidgetsFlutterBinding.ensureInitialized();

    //显示上面状态栏
    //SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,overlays: [SystemUiOverlay.top]);
    //显示下面导航栏
    //SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,overlays: [SystemUiOverlay.bottom]);

    if(_systemStatusBarIsShowLast!=null){
      if(_systemStatusBarIsShowLast==isShow
          && _systemStatusBarIsTextColorLightLast==isTextColorLight
          && _systemStatusBarBackgroundColorLast==backgroundColor
      ){
        return;
      }
    }

    _systemStatusBarIsShowLast=isShow;
    _systemStatusBarIsTextColorLightLast=isTextColorLight;
    _systemStatusBarBackgroundColorLast=backgroundColor;

    if(isShow){

      if(isTextColorLight){
        SystemChrome.setSystemUIOverlayStyle(
          SystemUiOverlayStyle.light.copyWith(
            statusBarColor: backgroundColor,
            //statusBarBrightness:Brightness.dark
          ),
        );
      }else{
        SystemChrome.setSystemUIOverlayStyle(
          SystemUiOverlayStyle.dark.copyWith(
            statusBarColor: backgroundColor,
            //statusBarBrightness:Brightness.light
          ),
        );
      }

      if(!isOnlyReSetColor){
        //注意:android第一次点击屏幕失效,需要点第二次
        SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,overlays: [SystemUiOverlay.top]);
      }

    }else{
      //全屏,隐藏状态栏和导航栏(
      // 实测: 商米T2,美团S4SP,无法再次划出底部导航栏,设置immersiveSticky/immersive也无效
      SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,overlays: []);
    }

  }



  /*
  保持设备唤醒状态 ANDROID IOS MACOS WINDOWS
  (实测: android编译错误)https://pub.dev/packages/wakelock_plus/install
  dependencies:
    wakelock_plus: ^1.1.1
  import 'package:wakelock_plus/wakelock_plus.dart';

  https://pub-web.flutter-io.cn/packages/flu_wake_lock
  dependencies:
    flu_wake_lock: ^0.0.2
  import 'package:flu_wake_lock/flu_wake_lock.dart';

  调节屏幕亮度: https://pub.flutter-io.cn/packages/screen_brightness
  保持屏幕常亮(android/iOS): https://pub.flutter-io.cn/packages/device_display_brightness

   */
  static void keepWakelock({bool isEnable=true}){

    if(isAndroidOriOSSystem()){
      FluWakeLock fluWakeLock = FluWakeLock();
      if(isEnable){
        //开启
        fluWakeLock.enable();
      }else{
        //关闭
        fluWakeLock.disable();
      }
    }

  }


  //跳转页面////////////////////////////////////////////////////////////////////////////

  //第三方路由操作: fluro库,可参考

/*
跳转页面,适用:动态路由
需要等待返回数据进一步处理:
startPage(context, XPage(()=>XPageState(xData:'kkkkk')),(result){
    });

//MaterialPageRoute不支持背景色透明(不推荐使用)
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => page,
    //跳转的页面设置 name（路由名字）才会允许别的页面指定返回到此页面
    settings: RouteSettings(name: page.toStringShort()),//XLogUtils.printLog(page.toStringShort());
  ),
);

//Navigator.of(context).restorablePush((context, arguments) => null) 用于静态路由?能否用于动态路由?
//Navigator.removeRoute(context, route)

静态路由(界面跳转: Navigator.restorablePushNamed(context, SettingsView.routeName);)
onGenerateRoute: (RouteSettings routeSettings) {
  return MaterialPageRoute<void>(
    settings: routeSettings,
    builder: (BuildContext context) {
      switch (routeSettings.name) {
        case SettingsView.routeName:
          return SettingsView(controller: settingsController);
        case SampleItemDetailsView.routeName:
          return const SampleItemDetailsView();
        case SampleItemListView.routeName:
        default:
          return const SampleItemListView();
      }
    },
  );
},

*/

  //XFlutterUtils.startPage(context,XPage(()=>Detail2StatePage()));
  //注意:此context为当前页的context,不能是其他page的context(其page销毁context变为null),
  static void startPage(
      BuildContext context,
      //Widget
      XPage page,
      {
        XPageCallback? resultCallback, //传入等待数据返回进一步处理,则传入响应方法(result){...}
        //跳转后是否关闭当前页
        bool isFinish=false,
        dynamic data,//传递到下一页的数据,获取final Map<String,dynamic> arguments = ModalRoute.of(context)?.settings.arguments as Map<String,dynamic>;
        XAnimationType pageAnimationType=XAnimationType.custom,//跳转页面动画
        String? cleanUntilRetainPageName, //在导航栈中,依次返回删除到该page(保留),如要清除所有已存在页面,则任意设置一个不存在的pageName
      }
      ) async
  {

    //注意: push的方式每次都会新建一个新的页面!
    //PageRouteBuilder支持背景色透明
    PageRouteBuilder pageRoute= PageRouteBuilder(
      //跳转背景透明路由
        opaque: false,//不透明,设置为false即透明
        transitionDuration: const Duration(milliseconds: 200),
        reverseTransitionDuration: const Duration(milliseconds: 200),
        //跳转的页面设置 name（路由名字）才会允许别的页面指定返回到此页面
        settings: RouteSettings(
          name: page.toStringShort(),
          arguments: data,//传递到下一页的数据,获取final Map<String,dynamic> arguments = ModalRoute.of(context)?.settings.arguments as Map<String,dynamic>;
        ),//XLogUtils.printLog(page.toStringShort());
        pageBuilder: (context, animation, secondaryAnimation) {
          //新页面
          //动画widget: SizeTransition,FadeTransition,RotationTransition,ScaleTransition,SlideTransition

          if(pageAnimationType==XAnimationType.custom){
            //自定义: 从右滑出+渐变

            //从左边滑入
            Offset fromLeft = const Offset(-1, 0.0);
            //从右边滑入
            Offset fromRight = const Offset(1.0, 0.0);
            //从下滑上
            Offset fromBottom = const Offset(0.0, 1.0);
            //从上滑下
            Offset fromTop = const Offset(0.0, -1.0);

            //开始位置
            Offset startOffset = fromRight;
            //结束位置(0,0,不偏移)
            Offset endOffset = Offset.zero;

            return SlideTransition(
              position: Tween<Offset>(
                begin: startOffset,
                end: endOffset,
              ).animate(animation),
              child: FadeTransition(
                opacity: Tween<double>(
                    begin: 0,
                    end: 1
                ).animate(animation),
                /*
                      Semantics 视力障碍者读屏功能
                      Semantics(
                        scopesRoute: true,
                        explicitChildNodes: true,
                        child: page,
                      )
                       */
                child: page,
              ),
            );
          }else if(pageAnimationType==XAnimationType.fade){
            //渐变清晰
            return FadeTransition(
                opacity: Tween<double>(begin: 0, end: 1).animate(animation),
                child: page
            );
          }else if(pageAnimationType==XAnimationType.rotation){
            //逆时针围绕中心旋转
            return RotationTransition(
              alignment: Alignment.center,
              turns: Tween<double>(begin: 0.5,end: 0.0).animate(animation),
              child: page,
            );
          }else if(pageAnimationType==XAnimationType.scale){
            //放大动画
            return ScaleTransition(
              alignment: Alignment.center,
              scale: Tween<double>(
                begin: 0,
                end: 1.0,
              ).animate(animation),
              child: page,
            );
          }else if(pageAnimationType==XAnimationType.slideLeft
              || pageAnimationType==XAnimationType.slideRight
              || pageAnimationType==XAnimationType.slideTop
              || pageAnimationType==XAnimationType.slideBottom
          ){
            //从左边滑入
            Offset fromLeft = const Offset(-1, 0.0);
            //从右边滑入
            Offset fromRight = const Offset(1.0, 0.0);
            //从下滑上
            Offset fromBottom = const Offset(0.0, 1.0);
            //从上滑下
            Offset fromTop = const Offset(0.0, -1.0);

            //开始位置
            Offset startOffset = fromLeft;

            if(pageAnimationType==XAnimationType.slideLeft){
              //滑向左边(从右边开始滑入)
              startOffset = fromRight;
            }else if(pageAnimationType==XAnimationType.slideRight){
              //滑向右边(从左边开始滑入)
              startOffset = fromLeft;
            }else if(pageAnimationType==XAnimationType.slideTop){
              //滑向上边(从下边开始滑入)
              startOffset = fromBottom;
            }else if(pageAnimationType==XAnimationType.slideBottom){
              //滑向下边(从上边开始滑入)
              startOffset = fromTop;
            }

            //结束位置(0,0,不偏移)
            Offset endOffset = Offset.zero;
            return SlideTransition(
              position: Tween<Offset>(
                begin: startOffset,
                end: endOffset,
              ).animate(animation),
              child: page,
            );
          }else{
            //没有动画
            return page;
          }
        });

    //注意:带Name的方法只适用静态路由使用,如:Navigator.of(context).pushNamed(routeName)
    if (resultCallback != null) {
      //等待返回数据接收处理,等同:
      // Navigator.of(context)
      //     .push(pageRoute)
      //     .then((result) async{
      //   String resultJson = await result as String;
      //   resultCallback(resultJson);
      // });
      dynamic result =await Navigator.of(context).push(pageRoute);
      //挂起等待返回结果
      resultCallback(result);
    }else if(!XStringUtils.isEmpty(cleanUntilRetainPageName)){
      //注意:这个方法会先创建一个新页面,然后退出栈中的其他页面(返回false则不保留)
      Navigator.of(context).pushAndRemoveUntil(pageRoute, (Route route){
        if(route.settings.name == cleanUntilRetainPageName){
          //保留页面,并停止继续删除栈中后面的页面
          return true;
        }else{
          //从栈顶开始判断,不保留页面
          return false;
        }
      });
    }else {
      //直接跳转,不需要处理返回值
      if(isFinish){
        //跳转并关闭当前页面(替换掉栈顶的页面),相同功能结果:popAndPushNamed和pushReplacementNamed
        Navigator.of(context).pushReplacement(pageRoute);
      }else{
        //跳转不关闭当前页
        Navigator.of(context).push(pageRoute);
      }

    }

  }

  //获得上一界面传递的数据(RouteSettings.arguments)
  static dynamic getPrePageDataFromRouteSettings(BuildContext context){
    //final Map<String,dynamic> arguments = ModalRoute.of(context)?.settings.arguments as Map<String,dynamic>;
    return ModalRoute.of(context)?.settings.arguments;
  }


/*
返回上一页面(如有动画则自动(按进入的)反方向效果)
适用:动态路由
 */
  static void finishPage(
      BuildContext context,
      {
        dynamic result,//携带数据返回上一界面响应
        String? backToPointPageName,//指定返回界面: 返回首页'/',  xsdk框架为具体XState名称
      }
      ) {

    //注意:带Name的方法只适用静态路由使用,如:Navigator.popAndPushNamed(context, backToPointPageName);

    //判断返回到首页则不能再退出了,再退则成空白页
    //替代Navigator.maybePop(),兼容双击退出app
    if(Navigator.canPop(context)){
      if(XStringUtils.isEmpty(backToPointPageName)){
        //返回上1界面
        Navigator.pop(context, result);
      }else{
        //返回指定界面,跳过的中间界面会被移除
        if('首页'==backToPointPageName){
          backToPointPageName='/';
        }

        Navigator.popUntil(context, ModalRoute.withName(backToPointPageName!));
      }
    }

  }

  /*
  backToNativePage
  返回原生界面,关闭整个flutter引擎(使用原生+flutter模式),
  如果是纯flutter,则会关闭整个app
   */
  static void closeFlutter(){
    //android ios有效,web端无效
    SystemNavigator.pop();
  }

  //退出整个app, android ios有效,web端无效
  static void closeApp(){
    exit(0);
  }

  /*
  返回系统桌面,仅支持android
   */
  static void backToScreen(){
    if(isAndroid()){
      SystemChannels.platform.invokeMethod('moveTaskToBack',false);
    }
  }



  ////////////////////////////////////////////////////////////

  //关闭输入法, 键盘 ok
  static void hideKeyboard() {

    if(applicationContext!=null){

      //创建一个没被任何控件使用的FocusNode
      //FocusScope.of(applicationContext!).requestFocus(FocusNode());

      //注意:这里不能传入page中的context,否则第二次打开键盘,无法使用api关闭
      FocusScopeNode currentFocus = FocusScope.of(applicationContext!);
      if (!currentFocus.hasPrimaryFocus && currentFocus.focusedChild != null) {
        FocusManager.instance.primaryFocus!.unfocus();
      }

    }else{
      //直接利用系统隐藏键盘(焦点依然在当前输入框)
      SystemChannels.textInput.invokeMethod<void>('TextInput.hide');
    }

  }
  static void showKeyboard(
      {
        FocusNode? node,
      }
      ) {

    if(node!=null){
      if(applicationContext!=null){
        FocusScope.of(applicationContext!).requestFocus(node);
      }else{
        node.requestFocus();
      }

    }else{
      //直接利用系统显示键盘(焦点依然在当前输入框)
      SystemChannels.textInput.invokeMethod<void>('TextInput.show');
    }

  }


/*
  打开侧栏
  注意: context要传入contextOfScaffold!(Scaffold的context,已在框架中赋值设置)
  如直接传入内部自带属性context并不是Scaffold的,会报错
   */
  static void openDrawer(BuildContext contextOfScaffold){
    Scaffold.maybeOf(contextOfScaffold)?.openDrawer();
  }
  static void closeDrawer(BuildContext contextOfScaffold){
    Scaffold.maybeOf(contextOfScaffold)?.closeDrawer();
  }
  static void openEndDrawer(BuildContext contextOfScaffold){
    Scaffold.maybeOf(contextOfScaffold)?.openEndDrawer();
  }
  static void closeEndDrawer(BuildContext contextOfScaffold){
    Scaffold.maybeOf(contextOfScaffold)?.closeEndDrawer();
  }

////////////////////////////////////////////////////////

/*
  打开外部的浏览器,电话,邮箱等, Android	iOS	Linux	macOS	Web	Windows
  https://pub-web.flutter-io.cn/packages/url_launcher/install
  dependencies:
    url_launcher: ^6.2.2
  import 'package:url_launcher/url_launcher.dart';

  iOS,Info.plist:
  <key>LSApplicationQueriesSchemes</key>
<array>
  <string>sms</string>
  <string>tel</string>
</array>

android,AndroidManifest.xml:
<!-- Provide required visibility configuration for API level 30 and above -->
<queries>
  <!-- If your app checks for SMS support -->
  <intent>
    <action android:name="android.intent.action.VIEW" />
    <data android:scheme="sms" />
  </intent>
  <!-- If your app checks for call support -->
  <intent>
    <action android:name="android.intent.action.VIEW" />
    <data android:scheme="tel" />
  </intent>
  <!-- If your application checks for inAppBrowserView launch mode support -->
  <intent>
    <action android:name="android.support.customtabs.action.CustomTabsService" />
  </intent>
</queries>


打开:
https://pub-web.flutter-io.cn/packages/url_launcher

打开浏览器(android:app内; iOS是appStore地址则打开appstore): uri='https://flutter.cn'
邮件: uri='mailto:smith@example.org?subject=News&body=New%20plugin'
电话: uri='tel:+1-555-010-999'
短信: uri='sms:5550101234'
文件: uri='file:/home';
微信 uri='weixin://'
   */
  static void openUri(String uri) async{

    try{
      Uri url = Uri.parse(uri);
      await launchUrl(url);
    }catch(e){
      XLogUtils.printLog('$e');
    }

  }

//复制到剪贴板
  static void copyToClipboard(String text){
    Clipboard.setData(ClipboardData(text: text));
  }


////////////////////////////////////////////////////////

/*
  uuid: https://pub.dev/packages/uuid
  支持 ANDROID IOS LINUX MACOS WEB WINDOWS
  dependencies:
    uuid: ^3.0.6
  import 'package:uuid/uuid.dart';
   */
  static String getUUID(){

/*
    // Generate a v1 (time-based) id
    uuid.v1(); // -> '6c84fb90-12c4-11e1-840d-7b25c5ee775a'

    // Generate a v4 (random) id
    uuid.v4(); // -> '110ec58a-a0f2-4ac4-8393-c866d813b8d1'

    // Generate a v5 (namespace-name-sha1-based) id
    uuid.v5(Uuid.NAMESPACE_URL, 'www.google.com'); // -> 'c74a196f-f19d-5ea9-bffd-a2742432fc9c'
     */
    return const Uuid().v1();

  }

/*
  日期选择器(自带)
  DateTime currentSelectDate=DateTime.now();
   */
  static void openDatePicker(
      {
        required BuildContext context,
//当前选中的日期
        DateTime? currentSelectDate,
//注意:按取消则返回null
        required XFunctionResultCallback<DateTime?> onCallbackResult,
/*
        // 自定义哪些日期可选
        (dayTime) {
        if (dayTime == DateTime(2022, 5, 6) ||
            dayTime == DateTime(2022, 6, 8)) {
          return false;
        }
        return true;
        },
         */
        SelectableDayPredicate? checkCanSelectDateFunction,
//选择日期模式,否则是选择年模式
        bool isDayMode=true,
//可选开始日期
        DateTime? startDate,
//可选结束日期
        DateTime? endDate,
      }
      )
  {
//
    showDatePicker(
      context:context,
      initialDate: currentSelectDate??DateTime.now(), // 初始化选中日期
      firstDate: startDate??DateTime(1900), // 开始日期
      lastDate: endDate??DateTime(3000), // 结束日期
      currentDate: DateTime.now(), // 系统当前日期
      selectableDayPredicate: checkCanSelectDateFunction,
//locale: const Locale('zh'),//中文显示,要设置代理,在app中配置,否则报错
// 日历弹框样式 calendar: 默认显示日历，可切换成输入模式，input:默认显示输入模式，可切换到日历，calendarOnly:只显示日历，inputOnly:只显示输入模式
      initialEntryMode: DatePickerEntryMode.calendar,
      initialDatePickerMode: isDayMode?DatePickerMode.day:DatePickerMode.year, // 日期选择模式 默认为天
      useRootNavigator: true, // 是否使用根导航器
      helpText: "请选择日期", // 左上角提示文字
      confirmText: "确认", // 确认按钮 文案
      cancelText: "取消", // 取消按钮 文案
      errorFormatText: "输入日期格式有误，请重新输入", // 输入日期 格式错误提示
      errorInvalidText: "输入日期不合法，请重新输入", // 输入日期 不在first 与 last 之间提示
      fieldLabelText: "输入所选日期", // 输入框上方 提示
      fieldHintText: "请输入日期", // 输入框为空时提示
//设置主题颜色
      builder: (context, child) {
        return child!;
      },
    ).then((DateTime? dateTime){
//注意:外部要更新选中日期,这样下次进入则是上次选中的日期
//setState(()=>_currentSelectDate=currentSelectDate);
      onCallbackResult(dateTime);
    });

  }

/*
  时间选择器(自带)
  TimeOfDay currentSelectTime=TimeOfDay.now();
   */
  static void openTimePicker(
      {
        required BuildContext context,
//当前选中的时间
        required TimeOfDay currentSelectTime,
//注意:按取消则返回null
        required XFunctionResultCallback<TimeOfDay?> onCallbackResult,
        SelectableDayPredicate? checkCanSelectDateFunction,
//选择日期模式,否则是选择年模式
        bool isDayMode=true,
//主题颜色
        MaterialColor? Colors_color,//Colors.amber
//可选开始日期
        DateTime? startDate,
//可选结束日期
        DateTime? endDate,
      }
      )
  {
//
    showTimePicker(
      context:context,
      initialTime: currentSelectTime, // 初始化选中事件 TimeOfDay.now()
//  默认显示时间，可切换成输入模式，input:默认显示输入模式
      initialEntryMode: TimePickerEntryMode.dial,
      useRootNavigator: true, // 是否使用根导航器
      helpText: "请选择时间", // 左上角提示文字
      confirmText: "确认", // 确认按钮 文案
      cancelText: "取消", // 取消按钮 文案
      errorInvalidText: "输入时间不合法，请重新输入", // 输入日期 不在first 与 last 之间提示
      hourLabelText: "输入小时", // 输入框上方 提示
      minuteLabelText: "输入分钟", // 输入框为空时提示
//设置主题颜色
      builder: Colors_color == null?null:(context, child) {
        return Theme(
          data: ThemeData(
            primarySwatch: Colors_color,
          ),
          child: child!,
        );
      },
    ).then((TimeOfDay? timeOfDayime){
//注意:外部要更新选中日期,这样下次进入则是上次选中的日期
//setState(()=>_currentSelectDate=currentSelectDate);
      onCallbackResult(timeOfDayime);
    });

  }


/*
  AlertDialog 警告 弹框-普通提示
  第三方:
  https://pub.flutter-io.cn/packages/rflutter_alert
  https://pub.flutter-io.cn/packages/cool_alert
  https://pub.flutter-io.cn/packages/flutter_platform_alert
  https://pub.flutter-io.cn/packages/quickalert

  注意:弹出Dialog,page会执行生命周期方法didChangeDependencies() onStart();

   */
  static void openAlertDialogDefault(
      {
        required BuildContext context,
        required String contentStr,
        String? title,
        XFunctionResultCallback<String>? onCallbackResult,
        String okButtonText='确定',
        String cancelButtonText='取消',
        bool isShowCancelButton=true,
        String closeButtonText='关闭',
//外部空白处是否能点击关闭
        bool canCancelAtOuter=false,
        EdgeInsetsGeometry? actionsPadding = const EdgeInsets.fromLTRB(0, 0, 20, 20),
      }
      )
  {

    List<Widget> buttonActions=[];

    if(onCallbackResult!=null){

      if(isShowCancelButton){
        buttonActions.add(
            TextButton(
              style: XStyleUtils.getButtonStyle(fontSize: XTempData.defaultTextFontSize,fontColor: XColorUtils.color666666),
              child: XWidgetUtils.getWidgetText(
                  cancelButtonText,
                  style: XStyleUtils.getTextStyle(color: XColorUtils.color666666,fontSize: XTempData.defaultTextFontSize)),
              onPressed: () {
//返回(关闭alterDialog)
                Navigator.of(context).pop();
//返回结果
                onCallbackResult(cancelButtonText);
              },
            )
        );
      }

      buttonActions.add(
          TextButton(
            style: XStyleUtils.getButtonStyle(fontSize: XTempData.defaultTextFontSize,fontColor: XColorUtils.appMainColor),
            onPressed: () {
//返回(关闭alterDialog)
              Navigator.of(context).pop();
//返回结果
              onCallbackResult(okButtonText);
            },
            child: XWidgetUtils.getWidgetText(
                okButtonText,
                style: XStyleUtils.getTextStyle(color: XColorUtils.appMainColor,fontSize: XTempData.defaultTextFontSize)
            ),
          )
      );

    }else{
      buttonActions.add(
          TextButton(
            style: XStyleUtils.getButtonStyle(fontSize: XTempData.defaultTextFontSize,fontColor: XColorUtils.appMainColor),
            child: XWidgetUtils.getWidgetText(
                closeButtonText,
                style: XStyleUtils.getTextStyle(color: XColorUtils.appMainColor,fontSize: XTempData.defaultTextFontSize)
            ),
            onPressed: () {
//返回(关闭alterDialog)
              Navigator.of(context).pop();
            },
          )
      );
    }

    Widget alertDialog=AlertDialog(
      title: title == null?null:XWidgetUtils.getWidgetText(
          title,
          style: XStyleUtils.getTextStyle(
            color: XColorUtils.color000000,
            fontSize: XTempData.defaultTitleFontSize,
          )
      ),
//可以自定义其他widget
      content: XWidgetUtils.getWidgetText(
          contentStr,
          style: XStyleUtils.getTextStyle(
            color: XColorUtils.color333333,
            fontSize: XTempData.defaultTextFontSize,
          )
      ),
      actions: buttonActions,
      shape: const RoundedRectangleBorder(
        borderRadius:  BorderRadius.all(Radius.circular(10)),
        side: BorderSide.none,
      ),
      actionsPadding:actionsPadding,
    );

//
    showDialog<void>(
      context: context,
      barrierDismissible: canCancelAtOuter, // 不能点击空白处关闭
      builder: (BuildContext context) {
        return canCancelAtOuter?alertDialog:PopScope(
          canPop:false,
          child: alertDialog,
          // onPopInvokedWithResult: (bool didPop,dynamic res){
          //   //3.22+支持
          //   //XLogUtils.printLog('---openAlertDialogDefault,拦截滑动返回,didPop=$didPop');
          // },
          // onPopInvoked: (bool didPop){
          //   //XLogUtils.printLog('---openAlertDialogDefault,拦截滑动返回,didPop=$didPop');
          // },
        );
      },
    );
  }


/*
  AlertDialog 弹框-自定义widget
   */
  static void openAlertDialogWithCustomWidget(
      {
        required BuildContext context,
        required Widget contentWidget,
        String? titleStr,
//外部空白处是否能点击关闭
        bool canCancelAtOuter=false,
      }
      )
  {

    Widget alertDialog=AlertDialog(
      title: titleStr == null?null:XWidgetUtils.getWidgetText(
          titleStr,
          style: XStyleUtils.getTextStyle(
            color: XColorUtils.color000000,
            fontSize: XTempData.defaultTitleFontSize,
          )
      ),
//可以自定义其他widget
      content: contentWidget,
      actions: <Widget>[
        TextButton(
          style: XStyleUtils.getButtonStyle(fontSize: XTempData.defaultTextFontSize,fontColor: XColorUtils.appMainColor),
          child: XWidgetUtils.getWidgetText(
              '关闭',
              style: XStyleUtils.getTextStyle(color: XColorUtils.appMainColor,fontSize: XTempData.defaultTextFontSize)
          ),
          onPressed: () {
            Navigator.of(context).pop();
          },
        ),
      ],
    );

    showDialog<void>(
      context: context,
      barrierDismissible: canCancelAtOuter,
      builder: (BuildContext context) {
        return canCancelAtOuter?alertDialog:PopScope(
            canPop:false,
            child: alertDialog,
            onPopInvoked:(bool didPop){
              //XLogUtils.printLog('---openAlertDialogDefault,拦截滑动返回,didPop=$didPop');
            }
        );
      },
    );
  }


/*
  AlertDialog 弹框-返回处理结果
   */
  static void openDialogWithTextField(
      {
        required BuildContext context,
        required String title,
        required XFunctionResultCallback<Map<String,dynamic>> onCallbackResult,
        String? defaultTextValue,
        String? hintText,
//外部空白处是否能点击关闭
        bool canCancelAtOuter=false,
        double? width,
        EdgeInsetsGeometry? actionsPadding = const EdgeInsets.fromLTRB(0, 0, 20, 20),
      }
      )
  {
    TextEditingController controller=TextEditingController(text: defaultTextValue);

    Widget textField=XWidgetUtils.getWidgetTextField(
      style:XStyleUtils.getTextStyle(
        color: XColorUtils.color000000,
        fontSize: XTempData.defaultTextFontSize,
      ),
      controller:controller,
      labelText: hintText,
      labelTextStyle: XStyleUtils.getTextStyle(
        color: XColorUtils.color999999,
        fontSize: XTempData.defaultTextFontSize,
      ),
    );

    Widget alertDialog=AlertDialog(
      title: XWidgetUtils.getWidgetText(
          title,
          style: XStyleUtils.getTextStyle(
            color: XColorUtils.color000000,
            fontSize: XTempData.defaultTitleFontSize,
          )
      ),
//可以自定义其他widget
      content: XWidgetUtils.getLayoutContainer(
          width: width,
          child: XWidgetUtils.getLayoutColumn(
              mainAxisSize:MainAxisSize.min,
              children: [
                textField,
                XWidgetUtils.getWidgetDivider(marginLeft: 0,marginRight: 0),
              ]
          )
      ),
      actions: <Widget>[
        TextButton(
          style: XStyleUtils.getButtonStyle(fontSize: XTempData.defaultTextFontSize,fontColor: XColorUtils.color666666),
          child: XWidgetUtils.getWidgetText(
              '取消',
              style: XStyleUtils.getTextStyle(color: XColorUtils.color666666,fontSize: XTempData.defaultTextFontSize)
          ),
          onPressed: () {
//返回(关闭alterDialog)
            Navigator.of(context).pop();
//返回结果
            onCallbackResult({'key':'取消'});
          },
        ),
        TextButton(
          style: XStyleUtils.getButtonStyle(fontSize: XTempData.defaultTextFontSize,fontColor: XColorUtils.appMainColor),
          child: XWidgetUtils.getWidgetText(
              '确定',
              style: XStyleUtils.getTextStyle(color: XColorUtils.appMainColor,fontSize: XTempData.defaultTextFontSize)
          ),
          onPressed: () {
//返回(关闭alterDialog)
            Navigator.of(context).pop();
//返回结果
            onCallbackResult({'key':'确定','value':controller.text});
          },
        ),

      ],
      actionsPadding:actionsPadding,
    );

//
    showDialog<void>(
      context: context,
      barrierDismissible: canCancelAtOuter, // 不能点击空白处关闭
      builder: (BuildContext context) {
        return canCancelAtOuter?alertDialog:PopScope(
            canPop:false,
            child: alertDialog,
            onPopInvoked:(bool didPop){
              //XLogUtils.printLog('---openAlertDialogDefault,拦截滑动返回,didPop=$didPop');
            }
        );
      },
    );
  }


/*
  SimpleDialog 弹框-自定义widget
   */
  static void openSimpleDialogWithCustomWidgets(
      {
        required BuildContext context,
        required Widget child,
//外部空白处是否能点击关闭
        bool canCancelAtOuter=false,
        String? titleStr,
        Color? backgroundColor,//=Colors.transparent,
      }
      )
  {
//
    Widget alertDialog=SimpleDialog(
      contentPadding: EdgeInsets.zero,
      title: titleStr == null?null:XWidgetUtils.getWidgetText(
          titleStr,
          style: XStyleUtils.getTextStyle(
            color: XColorUtils.color000000,
            fontSize: XTempData.defaultTitleFontSize,
          )
      ),
      backgroundColor: backgroundColor,
      surfaceTintColor: backgroundColor,//透明背景时不能只设置backgroundColor,否则会有痕迹
      children: [child],
    );

    showDialog<void>(
      context: context,
      barrierDismissible: canCancelAtOuter,
      builder: (BuildContext context) {
        return canCancelAtOuter?alertDialog:PopScope(
            canPop:false,
            child: alertDialog,
            onPopInvoked:(bool didPop){
              //XLogUtils.printLog('---openAlertDialogDefault,拦截滑动返回,didPop=$didPop');
            }
        );
      },
    );
  }

/*
  BottomSheet底部弹框
  使用showBottomSheet会报错:No Scaffold widget found.的原因:
    传入的context并不是Scaffold的context,要改为传入contextOfScaffold!(Scaffold的context,已在框架中赋值设置)即可,
    但其ui交互需要定义的比较多,这里改为使用showModalBottomSheet,则可以直接传入context或contextOfScaffold!,
    showModalBottomSheet默认点击空白处关闭,或手动关闭:

    //关闭
    XFlutterUtils.finishPage(context);
    //返回值
    widget.resultCallback(...);

圆角
shape: const RoundedRectangleBorder(
            borderRadius: BorderRadius.only(
              topLeft: Radius.circular(15),
              topRight: Radius.circular(15),
            )
        ),
   */
  static void openBottomSheet(
      {
        required BuildContext context,
        required XState widgetState,
        Color? backgroundColor,
        double? elevation,
        ShapeBorder? shape,
        bool enableDrag = true, //可拖拽关闭
        XFunctionResultCallback<dynamic>? onCallbackResult,
      }
      )
  {
    showModalBottomSheet(
      context: context,
      builder: (BuildContext context) {
//widgetState里面调用widget.resultCallback通知返回结果到上一页
//widgetState里面使用setState()更新自己即可
        return XStatefulWidget(()=>widgetState,resultCallback:onCallbackResult);
      },
      backgroundColor: backgroundColor,
      elevation:elevation,
      enableDrag:enableDrag,
      shape: shape,
    );
/*
    .then((dynamic value) {
      //结果返回上一页
      if(value!=null && callbackResult!=null){
        callbackResult(value as Map<String,dynamic>);
      }
    })
  实测:这种方式响应延迟,很慢,改为在widgetState调用widget.resultCallback() 通知返回结果:

  XFlutterUtils.finishPage(context);
  widget.resultCallback(...);
     */

  }

/*
  在任意位置显示浮层菜单(类似下拉菜单)
   */
  static void openMenu(
      {
        required BuildContext context,
        required double positionLeft,
        required double positionTop,
        required double positionRight,
        required double positionBottom,
        required List<dynamic> menuListData,
        required XFunctionWidget itemWidgetBuilder,
      }
      ){
    showMenu(
      context:context,
      position: RelativeRect.fromLTRB(positionLeft, positionTop, positionRight, positionBottom),
      items: menuListData.map((dynamic item){
        return PopupMenuItem(
          child: itemWidgetBuilder(item),
        );
      }).toList(),
    );
  }


/*
  loading框(Dialog方式),有可能误关闭新页面,改为在各自state中用Overlay实现
  关闭:Navigator.maybePop(context);
  注意:避免多次重复关闭,会退出当前界面
  static void openLoading(
      {
        required BuildContext context,
        //外部空白处是否能点击关闭
        bool canCancelAtOuter=true,
      }
      ) {
    //
    showDialog<void>(
      context: context,
      barrierDismissible: canCancelAtOuter,
      barrierColor: Colors.transparent,
      builder: (BuildContext context) {
        return const Center(
            child:SizedBox(
              width: 20,height: 20,
              child:CircularProgressIndicator(
                strokeWidth: 2.0,
                // backgroundColor: Colors.blue,
                // valueColor: AlwaysStoppedAnimation<Color>(Colors.red),
              ),
            )
        );
      },
    );

  }
  //注意:避免多次重复关闭,会退出当前界面
  static void closeLoading(BuildContext context){
    Navigator.maybePop(context);
  }
   */

/*
  图片选择器(相册,摄像头):
  image_picker: https://pub.dev/packages/image_picker
  dependencies:
    image_picker: ^0.8.9
  import 'package:image_picker/image_picker.dart';
 */
  static void openImagePicker(
      {
        required XFunctionResultCallback<XFile?> onCallbackResult,
        bool isCamera=false,

      }
      ) {
/*
    // Pick a video
    final XFile? image = await _picker.pickVideo(source: ImageSource.gallery);
    // Capture a video
    final XFile? video = await _picker.pickVideo(source: ImageSource.camera);
    // Pick multiple images
    final List<XFile>? images = await _picker.pickMultiImage();
     */
    ImagePicker picker = ImagePicker();
    if(isCamera){
//拍照
      picker.pickImage(source: ImageSource.camera).then((XFile? image) {
        onCallbackResult(image);
      });
    }else{
//相册选择
      picker.pickImage(source: ImageSource.gallery).then((XFile? image) {
        onCallbackResult(image);
      });
    }
  }

/*
  file文件选择器:
  https://pub-web.flutter-io.cn/packages/file_picker
   */



/*
震动 android iOS
https://pub-web.flutter-io.cn/packages/vibration
dependencies:
  vibration: ^1.8.4
 */

  static void vibrate() async{
    bool isOk=await Vibration.hasVibrator()??false;
    if (isOk) {
      Vibration.vibrate();
    }
  }

/*
  获得cpu内存使用情况
  https://pub-web.flutter-io.cn/packages/app_analysis/install
  dependencies:
    app_analysis: ^0.1.5
  仅支持android
   */
  static Future<Map<String,dynamic>> getCpuMemInfo() async{
    Map<String,dynamic> map={};

//仅支持android
    if(isAndroid()){
//cpu各个核心温度明细
//String cpu_temperature= (await CpuInfoProvider().temperature).toString();
//cpu平均温度(整体温度)
//String cpu_averageTemperature= (await CpuInfoProvider().averageTemperature).toString();
//cpu各个核心当前频率明细
      Map<int, double> cpu_currentFrequency= await CpuInfoProvider().currentFrequency;
      XLogUtils.printLog('---当前cpu各个核心频率=${cpu_currentFrequency.toString()}');
//cpu各个核心最大和最小频率范围明细
      Map<int, Extremum<double>> cpu_extremumFrequency= await CpuInfoProvider().extremumFrequency;
      XLogUtils.printLog('---cpu_extremumFrequency=${cpu_extremumFrequency.toString()}');

      StringBuffer sb=StringBuffer();

//当前cpu各个核心频率和使用情况占比%
      Map<String, Map<String,String>> cpu_frequency_use={};
      cpu_extremumFrequency.forEach( (key, value){
        double currentFrequency= cpu_currentFrequency[key]??0;
        double maxFrequency= value.max;
        double minFrequency= value.min;

        Map<String,String> itemFrequency={};
        if(maxFrequency!=0){
//cpu占用%
          itemFrequency['当前使用']='${XNumberUtils.d2int((currentFrequency/maxFrequency)*100)}%';
          sb.write('cpu$key:${itemFrequency['当前使用']}, ');
        }
        itemFrequency['当前频率']='$currentFrequency';
        itemFrequency['最大频率']='$maxFrequency';
        itemFrequency['最小频率']='$minFrequency';
        cpu_frequency_use['$key']=itemFrequency;

      } );
      XLogUtils.printLog('---当前cpu各个核心使用情况: ${XJsonUtils.obj2Json(cpu_frequency_use)}');

//内存信息
      final memInfo = await RamInfoProvider().info;
//全部内存
      int memInfo_total=memInfo.total.inMB.toInt();
//已使用内存
      int memInfo_used=memInfo.used.inMB.toInt();
//空闲内存
      int memInfo_available=memInfo.available.inMB.toInt();

//当前设备内存使用情况
      Map<String,dynamic> mem_use={};
//内存占用%
      if(memInfo_total!=0){
        mem_use['当前使用']='${XNumberUtils.d2int((memInfo_used/memInfo_total)*100)}%';
        sb.write('memory:${mem_use['当前使用']}');
      }
      mem_use['总内存']='${memInfo_total}MB';
      mem_use['已使用内存']='${memInfo_used}MB';
      mem_use['空闲内存']='${memInfo_available}MB';
      XLogUtils.printLog('---当前设备内存使用情况:${XJsonUtils.obj2Json(mem_use)}');

//cpu
      map['cpu']=cpu_frequency_use;
//内存
      map['memory']=mem_use;
      map['showUseInfo']=sb.toString();
    }

    return map;
  }

  /*
  重启app:
  https://pub-web.flutter-io.cn/packages/restart_app
  dependencies:
    restart_app: ^1.2.1
  import 'package:restart_app/restart_app.dart';
  注意:iOS关闭后会通知,需要手动点击再次打开,因此只用于android
   */
  static void restartApp(){
    if(isAndroid()){
      Restart.restartApp();
    }
  }

}
